﻿/* Copyright (c) 2011-2012, Zetatron Consulting
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

* Neither the name of Zetatron Consulting nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WorldServer.Models;
using AionCxxLibrary.DAO;
using AionCxxLibrary.Models;
using WorldServer.Data;
using AionCxxLibrary.Utilities;
using AionCxxLibrary.Enums;
using WorldServer.DAO;
using WorldServer.Packets.Server;
using WorldServer.Data.Templates;

namespace WorldServer.Services
{
    public class ItemService
    {
        public static bool CreateInitialItems(TCharacter ch)
        {
            ItemList items = Datastore.CharactersInitialData.ItemLists[((int)ch.Class / 3)];
            ch.Equipment = new List<TItem>();
            int notEquipSlot = 0;
            foreach (ItemIDCountPair idcp in items.Items)
            {
                ItemTemplate template = Datastore.ItemsById[idcp.ID];
                int slot = notEquipSlot;
                if (template.EquipmentSlot != EquipmentSlot.None)
                {
                    slot = (int)template.EquipmentSlot;
                    if (template.EquipmentSlot == EquipmentSlot.EitherHand)
                        slot = (int)EquipmentSlot.MainHand;
                }
                else if (template.ID != 182400001)
                    notEquipSlot++;
                else
                    slot = 0;

                long id = ItemDAO.InsertItem(ch.ID, slot, (int)StorageType.Cube, idcp.ID, idcp.Count, 0, "", 0, false, 0, 0, Item.OptionalSocket_None, template.EquipmentSlot != EquipmentSlot.None, true);
                if (id == 0)
                {
                    Log.Error("Resulting ItemInternalID=0 when creating initial items for " + ch.Name);
                    return false;
                }

                TItem item = new TItem();
                item.ItemID = idcp.ID;
                item.ItemColor = 0;

                if(template.WeaponType != WeaponType.NoWeapon && template.ArmorType != ArmorType.NoArmor)
                    ch.Equipment.Add(item);
            }
            return true;
        }

        public static Item CreateItemEx(int CharacterID, int Slot, int Storage, int TemplateID, long Count, int Color, string Crafter, int TempTradeTimeLeft, bool SoulBound, byte EnchantLevel, int SkinID, byte OptSocketFlag)
        {
            long internalId = ItemDAO.InsertItem(CharacterID, Slot, Storage, TemplateID, Count, Color, Crafter, TempTradeTimeLeft, SoulBound, EnchantLevel, SkinID, OptSocketFlag, false, false);
            Item result = new Item(internalId, TemplateID);
            result.Slot = Slot;
            result.Count = Count;
            result.Color = Color;
            result.Crafter = Crafter;
            result.TemporaryTradeTimeLeft = TempTradeTimeLeft;
            result.SoulBound = SoulBound;
            result.EnchantLevel = EnchantLevel;
            result.SkinID = SkinID;
            result.OptionalSocketFlag = OptSocketFlag;
            return result;
        }

        static void UpdateSlot(Storage storage, Item item, int newSlot)
        {
            item.Slot = newSlot;
            ItemDAO.UpdateItemSlot(item.InternalID, newSlot);
            Pkt_UpdateStorage(storage, item, false);
        }

        public static void MoveItem(Character c, int itemAionId, byte sourceStorage, byte destinationStorage, byte slot)
        {
            lock (c.Mutex_Item)
            {
                Storage StorageSource = c.Storages[(StorageType)(int)sourceStorage];
                Storage StorageDest = c.Storages[(StorageType)(int)destinationStorage];

                Item item = StorageSource.Find(it => it.GetGUID() == itemAionId);

                if (item == null)
                {
                    Log.Hack(c.Template.Name + " trying to move item " + itemAionId + " not existing.");
                    return;
                }

                if (item.Equipped)
                {
                    Log.Hack(c.Template.Name + " trying to move equipped item ...");
                    return;
                }

                Item DestinationExistingItem;

                // 1. See whether an item is present in the destination storage at the same slot.
                DestinationExistingItem = StorageDest.Find(i => i.Slot == slot && !i.Equipped);
                
                // 2. If an item exists, and is not kinah nor another instance of the same item, swap items.
                if (DestinationExistingItem != null && (DestinationExistingItem.TemplateID != item.TemplateID || DestinationExistingItem.Count == DestinationExistingItem.Template.MaxStackCount) && DestinationExistingItem.TemplateID != 182400001)
                {
                    byte currentSlot = (byte)item.Slot;
                    UpdateSlot(StorageSource, DestinationExistingItem, currentSlot);
                    UpdateSlot(StorageDest, item, slot);
                    // if other storage don't forget to send delete packets.
                }
                else if (DestinationExistingItem != null && DestinationExistingItem.TemplateID == item.TemplateID)
                {
                    Log.Hack("Stack merging through move!");
                    return;
                }
                else
                {
                    // Set the new slot
                    UpdateSlot(StorageDest, item, slot);
                }
            }
        }

        public static void DeleteItem(Character c, int guid)
        {
            lock (c.Mutex_Item)
            {
                Log.Debug(c.Template.Name + " ItemDelete " + guid);
                KeyValuePair<Storage, Item> item = c.LookupStorageItem(guid);
                if (item.Key != null && item.Value != null)
                {
                    item.Key.Remove(item.Value);
                    Pkt_DeleteFromStorage(item.Key, item.Value);
                }
            }
        }

        public static void UnequipItem(Character c, int guid)
        {
            lock (c.Mutex_Item)
            {
                KeyValuePair<Storage, Item> item = c.LookupStorageItem(guid);
                if (item.Key != null && item.Value != null)
                {
                    if (!item.Value.Equipped)
                    {
                        Log.Hack(c.Template.Name + " trying to unequip not equipped item!");
                        return;
                    }
                    int newslot = c.Inventory.LookupSlotFor(item.Value.TemplateID, 1);
                    if (newslot == 0xFFFF)
                    {
                        // TODO message no space in inventory
                        return;
                    }
                    item.Value.Equipped = false;
                    c.StatsList.RemoveBonus(item.Value, true);
                    ItemDAO.UpdateEquipped(item.Value.InternalID, false);
                    UpdateSlot(item.Key, item.Value, newslot);
                }
                else
                    Log.Hack(c.Template.Name + " trying to unequip inexistant item! (in his storage, trying to steal item from other player?)");
            }
        }

        static EquipmentSlot CalculateComboSlot(EquipmentSlot slot, Character c)
        {
            switch (slot)
            {
                case EquipmentSlot.EitherBattery:
                    if (c.Inventory.Find(it => it.Equipped && it.Slot == (int)EquipmentSlot.BatteryLeft) == null)
                        return EquipmentSlot.BatteryLeft;
                    else if(c.Inventory.Find(it => it.Equipped && it.Slot == (int)EquipmentSlot.BatteryRight) == null)
                        return EquipmentSlot.BatteryRight;
                    else
                        return EquipmentSlot.BatteryLeft;
                case EquipmentSlot.EitherEar:
                    if (c.Inventory.Find(it => it.Equipped && it.Slot == (int)EquipmentSlot.EarLeft) == null)
                        return EquipmentSlot.EarLeft;
                    else if(c.Inventory.Find(it => it.Equipped && it.Slot == (int)EquipmentSlot.EarRight) == null)
                        return EquipmentSlot.EarRight;
                    else
                        return EquipmentSlot.EarLeft;
                case EquipmentSlot.EitherFinger:
                    if (c.Inventory.Find(it => it.Equipped && it.Slot == (int)EquipmentSlot.FingerLeft) == null)
                        return EquipmentSlot.FingerLeft;
                    else if (c.Inventory.Find(it => it.Equipped && it.Slot == (int)EquipmentSlot.FingerRight) == null)
                        return EquipmentSlot.FingerRight;
                    else
                        return EquipmentSlot.FingerLeft;
                case EquipmentSlot.EitherHand:
                    if (c.Inventory.Find(it => it.Equipped && it.Slot == (int)EquipmentSlot.MainHand) == null)
                        return EquipmentSlot.MainHand;
                    else if (c.Inventory.Find(it => it.Equipped && it.Slot == (int)EquipmentSlot.SubHand) == null)
                        return EquipmentSlot.SubHand;
                    else
                        return EquipmentSlot.MainHand;
                default:
                    return EquipmentSlot.None;
            }
        }

        public static void EquipItem(Character c, int guid, int slot)
        {
            lock (c.Mutex_Item)
            {
                KeyValuePair<Storage, Item> item = c.LookupStorageItem(guid);
                if (item.Key != null && item.Value != null)
                {
                    if (item.Value.Equipped)
                    {
                        Log.Hack(c.Template.Name + " trying to equip an already equipped item!");
                        return;
                    }

                    if (!item.Value.SoulBound && item.Value.Template.SoulBindRequired)
                    {
                        c.UI.AskQuestion(95006, 0, delegate(bool ok)
                        {
                            if (ok)
                            {
                                item.Value.SoulBound = true;
                                ItemDAO.UpdateItemSoulBound(item.Value.InternalID, true);
                                EquipItem(c, guid, slot);
                            }
                        }, new AionDescription(item.Value.Template.NameID * 2 + 1));
                        return;
                    }

                    EquipmentSlot eqslot = (EquipmentSlot)slot;
                    if (eqslot == EquipmentSlot.EitherHand || eqslot == EquipmentSlot.EitherFinger || eqslot == EquipmentSlot.EitherEar || eqslot == EquipmentSlot.EitherHand)
                    {
                        eqslot = CalculateComboSlot(eqslot, c);
                    }
                    // check target slot is valid regarding item
                    if (((int)eqslot & (int)item.Value.Template.EquipmentSlot) != (int)eqslot)
                    {
                        Log.Hack(c.Template.Name + " trying to equip item " + guid + " in invalid slot.");
                        return;
                    }

                    // if item present in target destination, unequip it
                    Item existItem = c.Inventory.Find(it => it.Equipped && it.Slot == (int)eqslot);
                    if (existItem != null)
                    {
                        UnequipItem(c, existItem.GetGUID());
                    }

                    item.Value.Equipped = true;
                    c.StatsList.AddBonus(item.Value, true);
                    ItemDAO.UpdateEquipped(item.Value.InternalID, true);
                    UpdateSlot(item.Key, item.Value, (int)eqslot);
                }
                else
                    Log.Hack(c.Template.Name + " trying to equip inexistant item " + guid);
            }
        }

        public static void SwitchWeapons(Character c)
        {
            lock (c.Mutex_Item)
            {
                Item main = c.Inventory.Find(it => it.Equipped && it.Slot == (int)EquipmentSlot.MainHand);
                Item sub = c.Inventory.Find(it => it.Equipped && it.Slot == (int)EquipmentSlot.SubHand);
                Item mainoff = c.Inventory.Find(it => it.Equipped && it.Slot == (int)EquipmentSlot.OffMain);
                Item suboff = c.Inventory.Find(it => it.Equipped && it.Slot == (int)EquipmentSlot.OffSub);
                if (main != null)
                {
                    c.StatsList.RemoveBonus(main, true);
                    UpdateSlot(c.Inventory, main, (int)EquipmentSlot.OffMain);
                    Pkt_UpdateItem(c.Inventory, main);
                }
                if (sub != null)
                {
                    c.StatsList.RemoveBonus(sub, true);
                    UpdateSlot(c.Inventory, sub, (int)EquipmentSlot.OffSub);
                    Pkt_UpdateItem(c.Inventory, sub);
                }
                if (mainoff != null)
                {
                    c.StatsList.AddBonus(mainoff, true);
                    UpdateSlot(c.Inventory, mainoff, (int)EquipmentSlot.MainHand);
                    Pkt_UpdateItem(c.Inventory, mainoff);
                }
                if (suboff != null)
                {
                    c.StatsList.AddBonus(suboff, true);
                    UpdateSlot(c.Inventory, suboff, (int)EquipmentSlot.SubHand);
                    Pkt_UpdateItem(c.Inventory, suboff);
                }
            }
        }

        public static void AddItem(Character sender, int itemId, long count, bool allowInventoryExceed)
        {
            lock (sender.Mutex_Item)
            {
                ItemTemplate itemTemplate = Datastore.ItemsById[itemId];
                // Try to find an existing stack in this inventory.
                Item i1;
                long remainingCount = count;
                while ((i1 = sender.Inventory.Find(it => it.Template.ID == itemId && it.Count < it.Template.MaxStackCount)) != null && remainingCount > 0)
                {
                    long availableInStack = i1.Template.MaxStackCount - i1.Count;
                    if (availableInStack > 0)
                    {
                        long added = (remainingCount > availableInStack) ? availableInStack : remainingCount;
                        i1.Count += added;
                        ItemDAO.UpdateItemCount(i1.InternalID, i1.Count);
                        Pkt_UpdateItem(sender.Inventory, i1);
                        remainingCount -= added;
                    }
                }
                while (remainingCount > 0)
                {
                    int slot = 0;
                    bool optionalSlot = itemTemplate.OptionSlotBonus == 1;
                    if (optionalSlot && new Random().Next(100) > 50)
                        optionalSlot = false;

                    long newCnt = (remainingCount > itemTemplate.MaxStackCount) ? itemTemplate.MaxStackCount : remainingCount;

                    Item item = CreateItemEx(sender.GetGUID(), slot, (int)sender.Inventory.storageType, itemId, newCnt, 0, "", 0, false, 0, 0, optionalSlot ? (byte)1 : (byte)0);
                    sender.Inventory.Add(item);
                    Pkt_UpdateStorage(sender.Inventory, item, true);
                    remainingCount -= newCnt;
                }
            }
        }
        public static void AddItem(Character sender, string itemName, long count, bool allowInventoryExceed)
        {
            AddItem(sender, Datastore.ItemsByName[itemName].ID, count, allowInventoryExceed);
        }

        static void Pkt_UpdateItem(Storage storage, Item item)
        {
            if (storage.storageType == StorageType.Cube)
            {
                storage.Character.SendPacket(new SPkt_InventoryItemUpdate(item, false));
            }
            else
            {
                storage.Character.SendPacket(new SPkt_WarehouseItemUpdate(item, storage.storageType));
            }
        }

        static void Pkt_UpdateStorage(Storage storage, Item item, bool newItem)
        {
            if (storage.storageType == StorageType.Cube)
            {
                storage.Character.SendPacket(new SPkt_InventoryContentsUpdate(new Item[] { item }, newItem ? StorageDisplayType.NewItem : StorageDisplayType.Drop));
            }
            else
            {
                storage.Character.SendPacket(new SPkt_WarehouseContentsUpdate(item, storage.storageType));
            }
        }

        static void Pkt_DeleteFromStorage(Storage storage, Item item)
        {
            if (storage.storageType == StorageType.Cube)
            {
                storage.Character.SendPacket(new SPkt_InventoryItemDelete(item));
            }
            else
            {
                storage.Character.SendPacket(new SPkt_WarehouseItemDelete(item, storage.storageType));
            }
        }

    }
}
